home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / C / Applications / Newswatcher 2.0b22 / NW Source / Source / news.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-12-07  |  35.9 KB  |  1,386 lines  |  [TEXT/MMCC]

  1. /*----------------------------------------------------------------------------
  2.  
  3.     news.c
  4.  
  5.     This module handles all transactions with the NNTP server. It acts as
  6.     an interface between the rest of NewsWatcher and the resuable nntp.c
  7.     module.
  8.     
  9.     Copyright © 1994, Northwestern University.
  10.  
  11. ----------------------------------------------------------------------------*/
  12.  
  13. #include <stdlib.h>
  14. #include <stdio.h>
  15. #include <string.h>
  16.  
  17. #include "packages.h"
  18.  
  19. #include "glob.h"
  20. #include "dialog.h"
  21. #include "news.h"
  22. #include "nntp.h"
  23. #include "status.h"
  24. #include "newswatcher.h"
  25. #include "charset.h"
  26. #include "text.h"
  27. #include "strutil.h"
  28. #include "memutil.h"
  29. #include "cache.h"
  30. #include "windutil.h"
  31. #include "resutil.h"
  32. #include "fileutil.h"
  33. #include "ic.h"
  34.  
  35.  
  36.  
  37. #define kPostAuthDlg            136
  38. #define kStartupAuthDlg            137
  39. #define kAuthUsername            5
  40. #define kAuthPassword            7
  41.  
  42. #define kFileBufLen                (10*1024)
  43.  
  44.  
  45. static NntpStreamHandle    gNewsStream = nil;    /* handle to news server NNTP stream */
  46.  
  47. /* Globals used by CopyArticleToFile. */
  48.  
  49. static short gFileRefNum;            /* file ref num */
  50. static Ptr gFileBuf = nil;            /* pointer to output buffer */
  51. static Ptr gFileBufAux = nil;        /* pointer to auxiliary output buffer */
  52. static long gFileBufAuxSize;        /* number of bytes in aux buffer */
  53. static long gFileBufPos;            /* current position in output buffer */
  54. static long gFileLinePos;            /* position in current line */
  55. static char gFileLastChar;            /* last char */
  56. static Boolean gFileTruncate;        /* true to truncate if attached file */
  57. static Boolean gFileTruncated;        /* true if attached file found, should truncate */
  58. static TAttachedFileKind gFileKind;    /* kind of attached file */
  59. static Boolean gRequireEncodedTextBeginLine;    /* true if attached file must
  60.                                                    include "begin" flag line */
  61.  
  62.  
  63.  
  64. /*----------------------------------------------------------------------------
  65.     ReportServerError 
  66.     
  67.     Report server error to user.
  68.     
  69.     Entry:    nntpErr = NNTP error as returned by nntp.c.
  70.     
  71.     Entry:    function result = error code.
  72.     
  73.     If the NNTP error was an OS error, the error code is returned and it is
  74.     the responsibility of the calling code to report it.
  75.     
  76.     If the NNTP error was a server error, the error is reported to the user,
  77.     and the function result is userCanceledErr.
  78. ----------------------------------------------------------------------------*/
  79.  
  80. static OSErr ReportServerError (NntpErr nntpErr)
  81. {
  82.     CStr255 cmd, msg;
  83.     long num;
  84.     OSErr err = noErr;
  85.  
  86.     if (nntpErr == nntpServerErr) {
  87.         NntpGetServerErrInfo(cmd, &num, msg);
  88.         err = ServerErrorMessage(kStrNews, cmd, msg);
  89.         if (err != noErr) return err;
  90.         return userCanceledErr;
  91.     } else if (nntpErr == nntpOSErr) {
  92.         return NntpGetOSErr();
  93.     }
  94.     return noErr;
  95. }
  96.  
  97.  
  98.  
  99. /*----------------------------------------------------------------------------
  100.     SetServerOptions 
  101.     
  102.     Set news server options from preferences.
  103.     
  104.     Exit:    options = current server options.
  105. ----------------------------------------------------------------------------*/
  106.  
  107. static void SetServerOptions (NntpStreamOptions *options)
  108. {
  109.     MyICReadSharedPrefs(kICNewsAuthUsername);
  110.     MyICReadSharedPrefs(kICNewsAuthPassword);
  111.  
  112.     options->idleTime = 10;
  113.     options->useXPAT = gPrefs.useXPAT;
  114.     options->sendModeReader = !gPrefs.noModeReader;
  115.     options->batchedCmds = gPrefs.batchedGroupCmds;
  116.     options->newConnection = !gPrefs.noNewConnection;
  117.     options->authOnConnect = gPrefs.authAtStartup;
  118.     strcpy(options->username, gPrefs.authUsername);
  119.     strcpy(options->password, gPrefs.authPassword);
  120. }
  121.  
  122.  
  123.  
  124. /*----------------------------------------------------------------------------
  125.     GetAuthInfo 
  126.     
  127.     Get authorization information.
  128.  
  129.     Entry:    dlgID = dialog id.
  130.             statusMsg = status message, C-format, or nil if startup call.
  131.  
  132.     Exit:    function result = error code.
  133.             gPrefs.authUsername = authorization username.
  134.             gPrefs.authPassword = authorization password.
  135. ----------------------------------------------------------------------------*/
  136.  
  137. static OSErr GetAuthInfo (short dlgID, char *statusMsg)
  138. {
  139.     DialogPtr dlg = nil;
  140.     short item;
  141.     CStr255 tempStr;
  142.     short len;
  143.     char username[32];
  144.     char password[32];
  145.     OSErr err = noErr;
  146.  
  147.     MyICReadSharedPrefs(kICNewsAuthUsername);
  148.     MyICReadSharedPrefs(kICNewsAuthPassword);
  149.     
  150.     if (*gPrefs.authUsername != 0 && *gPrefs.authPassword != 0) return noErr;
  151.     
  152.     err = MyGetNewDialog(dlgID, ok, cancel, &dlg);
  153.     if (err != noErr) return err;
  154.     RestoreMovableModalDialogPosition(dlg, gPrefs.authLoc);
  155.     strcpy(username, gPrefs.authUsername);
  156.     DlgSetCString(dlg, kAuthUsername, username);
  157.     SetItemUSAsciiNoBlank(dlg, kAuthUsername);
  158.     SetItemMaxLength(dlg, kAuthUsername, 31);
  159.     strcpy(password, gPrefs.authPassword);
  160.     len = strlen(password);
  161.     memset(tempStr, '•', len);
  162.     tempStr[len] = 0;
  163.     DlgSetCString(dlg, kAuthPassword, tempStr);
  164.     SetItemPassword(dlg, kAuthPassword, password);
  165.     SetItemMaxLength(dlg, kAuthPassword, 31);
  166.     if (*username == 0) {
  167.         SelectDialogItemText(dlg, kAuthUsername, 0, 0);
  168.     } else if (*password == 0) {
  169.         SelectDialogItemText(dlg, kAuthPassword, 0, 0);
  170.     } else {
  171.         SelectDialogItemText(dlg, kAuthUsername, 0, 0x7fff);
  172.     }
  173.     
  174.     do {
  175.         DlgEnableItem(dlg, ok, *username != 0 && *password != 0);
  176.         MyMovableModalDialog(dlg, DialogFilter, &item);
  177.         if (item == kAuthUsername) DlgGetCString(dlg, item, username);
  178.     } while (item != ok && item != cancel);
  179.     
  180.     if (item == ok) {
  181.         strcpy(gPrefs.authUsername, username);
  182.         strcpy(gPrefs.authPassword, password);
  183.         MyICWriteSharedPrefs(kICNewsAuthUsername);
  184.         MyICWriteSharedPrefs(kICNewsAuthPassword);
  185.     }
  186.     SaveMovableModalDialogPosition(dlg, &gPrefs.authLoc);
  187.     err = DoClose(dlg);
  188.     if (err != noErr) return err;
  189.     if (item == ok && statusMsg != nil) {
  190.         err = DisplayStatusMessage(statusMsg);
  191.         if (err != noErr) return err;
  192.     }
  193.     return item == ok ? noErr : userCanceledErr;
  194. }
  195.  
  196.  
  197.  
  198. /*----------------------------------------------------------------------------
  199.     StartNNTP 
  200.     
  201.     Open a connection to the news server.
  202.     
  203.     Exit:    function result = error code.
  204. ----------------------------------------------------------------------------*/
  205.  
  206. OSErr StartNNTP (void)
  207. {
  208.     NntpErr nntpErr;
  209.     NntpStreamOptions options;
  210.     CStr255 cmd, response;
  211.     long responseCode;
  212.     OSErr err = noErr;
  213.     
  214.     MyICReadSharedPrefs(kICNNTPHost);
  215.     
  216.     while (true) {
  217.         gNewsStream = nil;
  218.         if (gPrefs.authAtStartup) {
  219.             err = GetAuthInfo(kStartupAuthDlg, nil);
  220.             if (err != noErr) return err;
  221.         }
  222.         err = DisplayStatusMessageNumber(kStrOpeningConnectionStatusMsg);
  223.         if (err != noErr) return err;
  224.         SetServerOptions(&options);
  225.         p2cstr(gPrefs.newsServerName);
  226.         nntpErr = NntpOpen((char*)gPrefs.newsServerName, &options, &gNewsStream);
  227.         c2pstr((char*)gPrefs.newsServerName);
  228.         if (gPrefs.authAtStartup && nntpErr == nntpServerErr) {
  229.             NntpGetServerErrInfo(cmd, &responseCode, response);
  230.             if (responseCode == 502) {
  231.                 *gPrefs.authPassword = 0;
  232.                 MyICWriteSharedPrefs(kICNewsAuthPassword);
  233.                 gNewsStream = nil;
  234.                 err = ServerErrorMessage(kStrNews, cmd, response);
  235.                 if (err != noErr) return err;
  236.             } else {
  237.                 goto exit;
  238.             }
  239.         } else if (nntpErr != nntpNoErr) {
  240.             goto exit;
  241.         } else {
  242.             break;
  243.         }
  244.     }
  245.     return noErr;
  246.     
  247. exit:
  248.  
  249.     gNewsStream = nil;
  250.     return ReportServerError(nntpErr);
  251. }
  252.  
  253.  
  254.  
  255. /*----------------------------------------------------------------------------
  256.     EndNNTP 
  257.     
  258.     Close the connection to the NNTP server.
  259. ----------------------------------------------------------------------------*/
  260.  
  261. void EndNNTP (void)
  262. {
  263.     if (gNewsStream != nil) NntpClose(gNewsStream);
  264.     gNewsStream = nil;
  265. }
  266.  
  267.  
  268.  
  269. /*----------------------------------------------------------------------------
  270.     GetGroupNames
  271.     
  272.     Get a list of group names from the server.
  273.     
  274.     Entry:    lastTime = 0: Fetch the entire full group list.
  275.             lastTime != 0: Fetch just the groups which have been created
  276.                 since lastTime - 36 hours.
  277.     
  278.     Exit:    function result = error code.
  279.             *strings = handle to C-format group name strings.
  280.             *numGroups = number of group names.
  281.     
  282.     The group names are mapped from Latin-1 to the Mac character set.
  283.     The group names are truncated to 127 characters.
  284.     Groups with status 'x' and '=' are filtered out (not returned).
  285. ----------------------------------------------------------------------------*/
  286.  
  287. OSErr GetGroupNames (unsigned long lastTime, Handle *strings, long *numGroups)
  288. {
  289.     NntpErr nntpErr;
  290.     Handle theStrings;
  291.     long theNumGroups;
  292.     
  293.     if (lastTime != 0) {
  294.     
  295.         /* Just get groups added since "lastTime". Subtract 36 hours to compensate 
  296.            for clock drift, daylight savings time, and server and client in 
  297.            different time zones. */
  298.            
  299.         lastTime -= 60L*60L*36L;
  300.     }
  301.     
  302.     nntpErr = NntpGetGroupNames(gNewsStream, lastTime, "x=",
  303.         &theStrings, &theNumGroups);
  304.     if (nntpErr != nntpNoErr) return ReportServerError(nntpErr);
  305.     
  306.     MapLatin1ToMacHandle(theStrings);
  307.     *strings = theStrings;
  308.     *numGroups = theNumGroups;
  309.     return noErr;
  310. }
  311.  
  312.  
  313.  
  314. /*----------------------------------------------------------------------------
  315.     GetArticle 
  316.     
  317.     Get one article from an NNTP server.
  318.     
  319.     Entry:    host = address of news server, or nil to use default server.
  320.             port = port number on news server if host not nil.
  321.             groupName = name of group, or nil if fetching by message id.
  322.             number = article number. Ignored if fetching by message id.
  323.             id = message id string, including < and > delimiters. Ignored
  324.                 if fetching by article number.
  325.             part = which part of the article to get:
  326.                 "ARTICLE": full article text, header and body.
  327.                 "HEAD": only article header.
  328.                 "BODY": only article body.
  329.             truncateIfAttachedFile = true to truncate the article if it
  330.                 contains an attached file. The BinHex or uuencode text
  331.                 for the attached file is not returned.
  332.             requireEncodedTextBeginLIne = true if BinHex or uuencode text
  333.                 must include special "begin" flag line.
  334.     
  335.     Exit:    function result = error code.
  336.             *text = handle to article text, or nil if article does not
  337.                 exit on server or group does not exist.
  338.             *textLength = length of article text.
  339.             *attachedFile = true if article contains an attached file which
  340.                 was truncated.
  341.                 
  342.     Backspace-underscore and underscore-backspace sequences are filtered out
  343.     of the article, as are all non-printable characters except for TAB and CR and FF.
  344.     Tabs are expanded to 8 column tab stops.
  345.     Trailing blank lines are deleted. 
  346.     CRLF line terminators are mapped to CR.
  347.     Leading double-period characters on lines are mapped to single periods.
  348.     8 bit characters are mapped from Latin-1.
  349. ----------------------------------------------------------------------------*/
  350.  
  351. OSErr GetArticle (char *host, short port,
  352.     char *groupName, long number, char *id, char *part, 
  353.     Boolean truncateIfAttachedFile, Boolean requireEncodedTextBeginLine, 
  354.     Handle *text, long *textLength, Boolean *attachedFile)
  355. {
  356.     NntpErr nntpErr;
  357.     long len, newLen, col, nSpace;
  358.     unsigned char **txt = nil;
  359.     unsigned char *p, *pEnd, *q, *r;
  360.     OSErr err = noErr;
  361.     NntpStreamHandle stream = nil;
  362.     CStr255 hostAndPortString;
  363.     
  364.     if (host == nil) {
  365.         stream = gNewsStream;
  366.     } else {
  367.         sprintf(hostAndPortString, "%s,%d", host, port);
  368.         nntpErr = NntpOpen(hostAndPortString, nil, &stream);
  369.         if (nntpErr != nntpNoErr) return ReportServerError(nntpErr);
  370.     }
  371.     nntpErr = NntpGetArticle(stream, groupName, number, id, part,
  372.         truncateIfAttachedFile, requireEncodedTextBeginLine,
  373.         (Handle*)&txt, attachedFile);
  374.     if (host != nil) {
  375.         NntpClose(stream);
  376.     }
  377.     if (nntpErr != nntpNoErr) {
  378.         if (nntpErr == nntpNoSuchGroupErr || nntpErr == nntpNoSuchArticleErr) {
  379.             *text = nil;
  380.             return noErr;
  381.         } else {
  382.             return ReportServerError(nntpErr);
  383.         }
  384.     }
  385.  
  386.     len = MyGetHandleSize(txt);
  387.     
  388.     for (p = *txt, pEnd = p + len, q = *txt; p < pEnd;) {
  389.         if (p < pEnd-1 && ((*p == BS && *(p+1) == '_') || (*p == '_' && *(p+1) == BS))) {
  390.             /* Filter underscore backspace and backspace underscore */
  391.             p += 2;
  392.         } else if (*p >= ' ' || *p == CR || *p == '\t' || *p == FF) {
  393.             /* Copy printable character as is */
  394.             *q++ = *p++;
  395.         } else {
  396.             /* Filter unprintable character */
  397.             p++;
  398.         }
  399.     }
  400.     
  401.     /* Trim trailing blank lines */
  402.     
  403.     q--;
  404.     while (q >= *txt && *q == CR) q--;
  405.     q++;
  406.     
  407.     len = q - *txt;
  408.     MySetHandleSize(txt, len);
  409.     
  410.     /* Map Latin1 */
  411.     
  412.     MapLatin1ToMacHandle((Handle)txt);
  413.     
  414.     /* Expand tabs. */
  415.     
  416.     newLen = len;
  417.     r = *txt;
  418.     for (p = *txt, pEnd = p + len; p < pEnd; p++) {
  419.         if (*p == '\t') {
  420.             col = p - r;
  421.             nSpace = 8 - (col % 8);
  422.             newLen += nSpace - 1;
  423.             r = p+1;
  424.         } else if (*p == CR) {
  425.             r = p+1;
  426.         }
  427.     }
  428.     if (newLen > len) {
  429.         err = MySetHandleSize(txt, newLen);
  430.         if (err != noErr) goto exit;
  431.         BlockMoveData(*txt, *txt + newLen - len, len);
  432.         r = *txt + newLen - len;;
  433.         for (p = r, pEnd = p + len, q = *txt; p < pEnd; p++) {
  434.             if (*p == '\t') {
  435.                 col = p - r;
  436.                 nSpace = 8 - (col % 8);
  437.                 while (nSpace--) *q++ = ' ';
  438.                 r = p+1;
  439.             } else if (*p == CR) {
  440.                 *q++ = *p;
  441.                 r = p+1;
  442.             } else {
  443.                 *q++ = *p;
  444.             }
  445.         }
  446.         len = newLen;
  447.     }
  448.     
  449.     *text = (Handle)txt;
  450.     *textLength = len;
  451.     return noErr;
  452.     
  453. exit:
  454.  
  455.     MyDisposeHandle(txt);
  456.     return err;
  457. }
  458.  
  459.  
  460.  
  461. /*----------------------------------------------------------------------------
  462.     TextHasAttachedFile 
  463.     
  464.     Check article text to see if it includes an attached file.
  465.             
  466.     Entry:    text = pointer to article text.
  467.             length = length of text.
  468.     
  469.     Exit:    *fileKind = kind of attached file.
  470. ----------------------------------------------------------------------------*/
  471.  
  472. static void TextHasAttachedFile (Ptr text, long length, TAttachedFileKind *fileKind)
  473. {
  474.     char *p, *pEnd, *q;
  475.     
  476.     for (p = text, pEnd = p + length; p < pEnd; p++) {
  477.         if (*p == CR) {
  478.             q = p+1;
  479.             if (q >= pEnd) break;
  480.             if (*q == '(') {
  481.                 if (strncmp(q, "(This file must be converted with BinHex", 39) == 0) {
  482.                     *fileKind = kBinHex;
  483.                     return;
  484.                 }
  485.             } else if (*q == 'b') {
  486.                 if (strncmp(q, "begin ", 6) == 0) {
  487.                     q += 6;
  488.                     if (q >= pEnd) break;
  489.                     if (isoctal(*q) && isoctal(*(q+1)) && isoctal(*(q+2))) {
  490.                         *fileKind = kUUEncode;
  491.                         return;
  492.                     }
  493.                 }
  494.             }
  495.         }
  496.     }
  497.     *fileKind = kNoAttachedFile;
  498. }
  499.  
  500.  
  501.  
  502. /*----------------------------------------------------------------------------
  503.     CheckForAttachedFile 
  504.     
  505.     Check for BinHex or UUEncode text.
  506.     
  507.     Entry:    t = pointer to text.
  508.             len = length of text.
  509.             gRequireEncodedTextBeginLine = true if the encoded text
  510.                 must include a begin "flag" line.
  511.     
  512.     Exit:    function result = true if BinHex or UUEncode text encountered.
  513.             *pos = offset in text of beginning of BinHex or UUEncode text.
  514.             
  515.     This function is nearly identical to the one with the same name in
  516.     nntp.c. They really should be combined some day.
  517. ----------------------------------------------------------------------------*/
  518.  
  519. static Boolean CheckForAttachedFile (Ptr t, long len, long *pos)
  520. {
  521.     long lenA, lenB;
  522.     unsigned char *p, *pEnd, *q, *r;
  523.     unsigned char *flagLine, *lineA, *lineB, *nextLine;
  524.     Boolean haveBeginFlagLine, beginFlagLineIsBinHex, foundIt;
  525.  
  526.     p = (unsigned char*)t;
  527.     pEnd = p + len - 1;
  528.     
  529.     while (p < pEnd && *p != CR) p++;
  530.     p++;
  531.     if (p >= pEnd) return false;
  532.     
  533.     while (p < pEnd) {
  534.         if (gRequireEncodedTextBeginLine) {
  535.             haveBeginFlagLine = false;
  536.             flagLine = p;
  537.             while (flagLine < pEnd) {
  538.                 q = flagLine;
  539.                 while (q < pEnd && *q != CR) q++;
  540.                 q += 1;
  541.                 if (q >= pEnd) break;
  542.                 if (*flagLine == '(') {
  543.                     if (flagLine + 39 >= pEnd) break;
  544.                     if (strncmp((char*)flagLine, 
  545.                         "(This file must be converted with BinHex", 39) == 0) 
  546.                     {
  547.                         haveBeginFlagLine = true;
  548.                         beginFlagLineIsBinHex = true;
  549.                         break;
  550.                     }
  551.                 } else if (*flagLine == 'b') {
  552.                     if (flagLine + 9 >= pEnd) break;
  553.                     if (strncmp((char*)flagLine, "begin ", 6) == 0) {
  554.                         r = flagLine + 6;
  555.                         if (isoctal(*r) && isoctal(*(r+1)) && isoctal(*(r+2))) {
  556.                             haveBeginFlagLine = true;
  557.                             beginFlagLineIsBinHex = false;
  558.                             break;
  559.                         }
  560.                     }
  561.                 } 
  562.                 flagLine = q;
  563.             }
  564.             if (!haveBeginFlagLine) return false;
  565.             p = flagLine;
  566.             lineA = q;
  567.             while (lineA < pEnd && *lineA == CR) lineA++;
  568.             if (lineA >= pEnd) break;
  569.         } else {
  570.             lineA = p;
  571.         }
  572.         q = lineA;
  573.         while (q < pEnd && *q != CR) q++;
  574.         lenA = q - lineA;
  575.         lineB = q + 1;
  576.         while (lineB < pEnd && *lineB == CR) lineB++;
  577.         if (lineB >= pEnd) break;
  578.         nextLine = gRequireEncodedTextBeginLine ? lineA : lineB;
  579.         if (lenA < 60) {
  580.             p = nextLine;
  581.             continue;
  582.         }
  583.         q = lineB;
  584.         while (q < pEnd && *q != CR) q++;
  585.         if (q >= pEnd) break;
  586.         lenB = q - lineB;
  587.         if (lenA != lenB) {
  588.             p = nextLine;
  589.             continue;
  590.         }
  591.         if (gRequireEncodedTextBeginLine) {
  592.             if (beginFlagLineIsBinHex) {
  593.                 foundIt = IsLegalBinHexLine(lineA, lenA) && IsLegalBinHexLine(lineB, lenB);
  594.             } else {
  595.                 foundIt = IsLegalUULine(lineA, lenA) && IsLegalUULine(lineB, lenB);
  596.             }
  597.         } else {
  598.             foundIt = (IsLegalBinHexLine(lineA, lenA) && IsLegalBinHexLine(lineB, lenB)) ||
  599.                 (IsLegalUULine(lineA, lenA) && IsLegalUULine(lineB, lenB));
  600.         }
  601.         if (foundIt) {
  602.             *pos = (char*)lineA - t;
  603.             return true;
  604.         } else {
  605.             p = nextLine;
  606.             continue;
  607.         }
  608.     }
  609.     
  610.     return false;
  611. }
  612.  
  613.  
  614.  
  615. /*----------------------------------------------------------------------------
  616.     FlushFileBuf 
  617.     
  618.     Flush the file buffer.
  619.     
  620.     Entry:    len = length of buffer.
  621.     
  622.     Exit:    function result = error code.
  623. ----------------------------------------------------------------------------*/
  624.  
  625. static OSErr FlushFileBuf (long len)
  626. {
  627.     OSErr err = noErr;
  628.     long headLen, tailLen, pos;
  629.     
  630.     MapLatin1ToMacPtr(gFileBuf, len);
  631.     
  632.     if (gFileTruncate) {
  633.     
  634.         if (gFileBufAuxSize > 0) {
  635.             headLen = len > 200 ? 200 : len;
  636.             BlockMoveData(gFileBuf, gFileBufAux + gFileBufAuxSize, headLen);
  637.             gFileBufAuxSize += headLen;
  638.             if (CheckForAttachedFile(gFileBufAux, gFileBufAuxSize, &pos)) {
  639.                 if (pos > 0) {
  640.                     err = MyFSWriteNoCache(gFileRefNum, &pos, gFileBufAux, GiveTime);
  641.                     if (err != noErr) return err;
  642.                 }
  643.                 gFileTruncated = true;
  644.                 return noErr;
  645.             }
  646.             gFileBufAuxSize -= headLen;
  647.             err = MyFSWriteNoCache(gFileRefNum, &gFileBufAuxSize, gFileBufAux, GiveTime);
  648.             if (err != noErr) return err;
  649.         }
  650.         if (CheckForAttachedFile(gFileBuf, len, &pos)) {
  651.             if (pos > 0) {
  652.                 err = MyFSWriteNoCache(gFileRefNum, &pos, gFileBuf, GiveTime);
  653.                 if (err != noErr) return err;
  654.             }
  655.             gFileTruncated = true;
  656.             return noErr;
  657.         }
  658.         tailLen = len > 200 ? 200 : len;
  659.         len -= tailLen;
  660.         BlockMoveData(gFileBuf + len, gFileBufAux, tailLen);
  661.         gFileBufAuxSize = tailLen;
  662.         if (len > 0) {
  663.             err = MyFSWriteNoCache(gFileRefNum, &len, gFileBuf, GiveTime);
  664.             if (err != noErr) return err;
  665.         }
  666.     
  667.     } else {
  668.  
  669.         err = MyFSWriteNoCache(gFileRefNum, &len, gFileBuf, GiveTime);
  670.         if (err != noErr) return err;
  671.         if (gFileKind == kNoAttachedFile) {
  672.             if (gFileBufAuxSize > 0) {
  673.                 headLen = len > 200 ? 200 : len;
  674.                 BlockMoveData(gFileBuf, gFileBufAux + gFileBufAuxSize, headLen);
  675.                 gFileBufAuxSize += headLen;
  676.                 TextHasAttachedFile(gFileBufAux, gFileBufAuxSize, &gFileKind);
  677.             }
  678.             if (gFileKind == kNoAttachedFile) 
  679.                 TextHasAttachedFile(gFileBuf, len, &gFileKind);
  680.             if (gFileKind == kNoAttachedFile) {
  681.                 tailLen = len > 200 ? 200 : len;
  682.                 BlockMoveData(gFileBuf + len - tailLen, gFileBufAux, tailLen);
  683.                 gFileBufAuxSize = tailLen;
  684.             }
  685.         }
  686.         
  687.     }
  688.     
  689.     return noErr;
  690. }
  691.  
  692.  
  693.  
  694. /*----------------------------------------------------------------------------
  695.     CopyArticleToFileChunkFunction 
  696.     
  697.     Copy one article chunk to a file.
  698.     
  699.     Entry:    t = pointer to chunk.
  700.             tLen = length of chunk.
  701.     
  702.     Exit:    function result = error code.
  703.             *abort = true to abort the operation.
  704. ----------------------------------------------------------------------------*/
  705.  
  706. static OSErr CopyArticleToFileChunkFunction (register Ptr t, register long tLen,
  707.     Boolean *abort)
  708. {
  709.     register char c, d;
  710.     register Ptr p;
  711.     Ptr pEnd;
  712.     short nSpace;
  713.     OSErr err = noErr;
  714.     
  715.     *abort = false;
  716.  
  717.     if (tLen == 0) return noErr;
  718.     
  719.     if (gFileLastChar != 0) {
  720.         c = gFileLastChar;
  721.     } else { 
  722.         c = *t;
  723.         t++;
  724.         tLen--;
  725.     }
  726.     
  727.     p = gFileBuf + gFileBufPos;
  728.     pEnd = gFileBuf + kFileBufLen - 10;
  729.     
  730.     while (tLen > 0) {
  731.         if (p >= pEnd) {
  732.             err = FlushFileBuf(p - gFileBuf);
  733.             if (err != noErr) return err;
  734.             if (gFileTruncated) {
  735.                 *abort = true;
  736.                 return noErr;
  737.             }
  738.             p = gFileBuf;
  739.         }
  740.         d = *t;
  741.         if (c == BS && d == '_' || c == '_' && d == BS) {
  742.             t++;
  743.             c = *t;
  744.             t++;
  745.             tLen -= 2;
  746.         } else if (c == CR && d == LF) {
  747.             *p++ = CR;
  748.             t++;
  749.             c = *t;
  750.             t++;
  751.             tLen -= 2;
  752.             gFileLinePos = 0;
  753.         } else if (gFileLinePos == 0 && c == '.' && d == '.') {
  754.             *p++ = '.';
  755.             t++;
  756.             c = *t;
  757.             t++;
  758.             tLen -= 2;
  759.             gFileLinePos = 1;
  760.         } else if (c == '\t') {
  761.             nSpace = 8 - (gFileLinePos % 8);
  762.             while (nSpace--) *p++ = ' ';
  763.             gFileLinePos += nSpace;
  764.             c = d;
  765.             t++;
  766.             tLen--;
  767.         } else if (c >= ' ' || c == CR || c == FF || c < 0) {
  768.             *p++= c;
  769.             gFileLinePos++;
  770.             c = d;
  771.             t++;
  772.             tLen--;
  773.         } else {
  774.             c = d;
  775.             t++;
  776.             tLen--;
  777.         }
  778.     }
  779.     
  780.     if (tLen == 0) {
  781.         gFileLastChar = c;
  782.     } else {
  783.         gFileLastChar = 0;
  784.     }
  785.     gFileBufPos = p - gFileBuf;
  786.     return noErr;
  787. }
  788.  
  789.  
  790.  
  791. /*----------------------------------------------------------------------------
  792.     CopyArticleToFile 
  793.     
  794.     Get one article from the NNTP server and copy it to a file.
  795.     
  796.     Entry:    groupName = name of group, or nil if fetching by message id.
  797.             number = article number. Ignored if fetching by message id.
  798.             id = message id string, including < and > delimiters. Ignored
  799.                 if fetching by article number.
  800.             part = which part of the article to get:
  801.                 "ARTICLE": full article text, header and body.
  802.                 "HEAD": only article header.
  803.                 "BODY": only article body.
  804.             refNum = reference number of open file.
  805.             truncateIfAttachedFile = true to truncate the article if it
  806.                 contains an attached file. The BinHex or uuencode text
  807.                 for the attached file is not copied to the file.
  808.             requireEncodedTextBeginLine = true if BinHex or uuencode text
  809.                 must include special "begin" flag line.
  810.     
  811.     Exit:    function result = error code.
  812.             *fileKind = attached file kind, if !truncateIfAttachedFile.
  813.                 
  814.     Backspace-underscore and underscore-backspace sequences are filtered out
  815.     of the article, as are all non-printable characters except for TAB and CR and FF.
  816.     Tabs are expanded to 8 column tab stops.
  817.     CRLF line terminators are mapped to CR.
  818.     Leading double-period characters on lines are mapped to single periods.
  819.     8 bit characters are mapped from Latin-1.
  820. ----------------------------------------------------------------------------*/
  821.  
  822. OSErr CopyArticleToFile (char *groupName, long number, char *id, char *part, 
  823.     short refNum, Boolean truncateIfAttachedFile, 
  824.     Boolean requireEncodedTextBeginLine, TAttachedFileKind *fileKind)
  825. {
  826.     NntpErr nntpErr;
  827.     OSErr err = noErr;
  828.     Boolean aborted;
  829.     
  830.     gFileRefNum = refNum;
  831.     err = MyNewPtr(kFileBufLen, &gFileBuf);
  832.     if (err != noErr) goto exit;
  833.     err = MyNewPtr(400, &gFileBufAux);
  834.     if (err != noErr) goto exit;
  835.     gFileBufAuxSize = 0;
  836.     gFileBufPos = 0;
  837.     gFileLinePos = 0;
  838.     gFileLastChar = 0;
  839.     gFileTruncate = truncateIfAttachedFile;
  840.     gFileTruncated = false;
  841.     gFileKind = kNoAttachedFile;
  842.     gRequireEncodedTextBeginLine = requireEncodedTextBeginLine;
  843.     
  844.     nntpErr = NntpGetChunkyArticle(gNewsStream, groupName, number, id, part,
  845.         CopyArticleToFileChunkFunction, &aborted);
  846.     if (nntpErr != nntpNoErr) {
  847.         if (nntpErr == nntpNoSuchGroupErr || nntpErr == nntpNoSuchArticleErr) {
  848.             *fileKind = kArtNotOnServer;
  849.             goto exit;
  850.         } else {
  851.             err = ReportServerError(nntpErr);
  852.             goto exit;
  853.         }
  854.     }
  855.  
  856.     if (!gFileTruncated) {
  857.         if (gFileLastChar != 0) {
  858.             *(gFileBuf + gFileBufPos) = gFileLastChar;
  859.             gFileBufPos++;
  860.         }
  861.         if (gFileBufPos > 0) err = FlushFileBuf(gFileBufPos);
  862.         if (gFileTruncate && gFileBufAuxSize > 0) {
  863.             err = MyFSWriteNoCache(gFileRefNum, &gFileBufAuxSize, gFileBufAux, GiveTime);
  864.             if (err != noErr) goto exit;
  865.         }
  866.     }
  867.     
  868.     *fileKind = gFileKind;
  869.     
  870. exit:
  871.     
  872.     MyDisposePtr(gFileBuf);
  873.     MyDisposePtr(gFileBufAux);
  874.     gFileBuf = nil;
  875.     gFileBufAux = nil;
  876.     return err;
  877. }
  878.  
  879.  
  880.  
  881. /*----------------------------------------------------------------------------
  882.     PostArticle 
  883.     
  884.     Post an article.
  885.     
  886.     Entry:    text = handle to article text, including header.
  887.             statusMsg = status message, C-format.
  888.             
  889.     Exit:    function result = error code.
  890.             postIndeterminate = true if entire article sent, but error occured
  891.                 or user canceled before final server response received. The
  892.                 article may or may not have been posted successfully.
  893. ----------------------------------------------------------------------------*/
  894.  
  895. OSErr PostArticle (Handle text, char *statusMsg, Boolean *postIndeterminate)
  896. {
  897.     NntpErr nntpErr;
  898.     CStr255 cmd, response;
  899.     long responseCode;
  900.     OSErr err = noErr;
  901.     
  902.     MyICReadSharedPrefs(kICNewsAuthUsername);
  903.     MyICReadSharedPrefs(kICNewsAuthPassword);
  904.  
  905.     nntpErr = NntpPostArticle(gNewsStream, text, postIndeterminate);
  906.     if (nntpErr == nntpServerErr) {
  907.         NntpGetServerErrInfo(cmd, &responseCode, response);
  908.         if (responseCode == 480) {
  909.             while (true) {
  910.                 err = GetAuthInfo(kPostAuthDlg, statusMsg);
  911.                 if (err != noErr) return err;
  912.                 nntpErr = NntpAuthorize(gNewsStream, gPrefs.authUsername, gPrefs.authPassword);
  913.                 if (nntpErr == nntpServerErr) {
  914.                     NntpGetServerErrInfo(cmd, &responseCode, response);
  915.                     if (responseCode == 502) {
  916.                         err = ServerErrorMessage(kStrNews, cmd, response);
  917.                         if (err != noErr) return err;
  918.                         *gPrefs.authPassword = 0;
  919.                     } else {
  920.                         goto exit;
  921.                     }
  922.                 } else if (nntpErr != nntpNoErr) {
  923.                     goto exit;
  924.                 } else {
  925.                     break;
  926.                 }
  927.             }
  928.             nntpErr = NntpPostArticle(gNewsStream, text, postIndeterminate);
  929.             if (nntpErr != nntpNoErr) goto exit;
  930.         } else {
  931.             goto exit;
  932.         }
  933.     } else if (nntpErr != nntpNoErr) {
  934.         goto exit;
  935.     }
  936.     return noErr;
  937.  
  938. exit:
  939.  
  940.     return ReportServerError(nntpErr);
  941. }
  942.  
  943.  
  944.  
  945. /*----------------------------------------------------------------------------
  946.     GetGroupArticleRange 
  947.     
  948.     Query the NNTP server to get the current article range for a single group.
  949.     
  950.     Entry:    theGroup = pointer to group record.
  951.     
  952.     Exit:    function result = error code.
  953.             *groupExits = true if group exists.
  954.     
  955.             theGroup->firstMess = first article in range.
  956.             theGroup->lastMess = last article in range.
  957.             theGroup->numUnread = number of articles in range.
  958. ----------------------------------------------------------------------------*/
  959.  
  960. OSErr GetGroupArticleRange (TGroup *theGroup, Boolean *groupExists)
  961. {
  962.     CStr255 groupName;
  963.     NntpErr nntpErr;
  964.     long first, last, count;
  965.     OSErr err = noErr;
  966.     
  967.     *groupExists = true;
  968.     strcpy(groupName, *gGroupNames + theGroup->nameOffset);
  969.     nntpErr = NntpGetGroupInfo(gNewsStream, groupName, &first, &last, &count);
  970.     if (nntpErr != nntpNoErr) {
  971.         if (nntpErr == nntpNoSuchGroupErr) {
  972.             *groupExists = false;
  973.             return noErr;
  974.         } else {
  975.             return ReportServerError(nntpErr);
  976.         }
  977.     }
  978.     
  979.     if (first == 0 && last == 0) {
  980.         /* Special case empty group: set firstMess = lastMess + 1. */
  981.         theGroup->firstMess = theGroup->lastMess + 1;
  982.         err = AgeCache(groupName, 0x7fffffff);
  983.         if (err != noErr) return err;
  984.     } else {
  985.         if (first <= 0) first = 1;
  986.         if (last < first) last = first - 1;
  987.         theGroup->firstMess = first;
  988.         theGroup->lastMess = last;
  989.         err = AgeCache(groupName, first);
  990.         if (err != noErr) return err;
  991.     } 
  992.     theGroup->numUnread = theGroup->lastMess - theGroup->firstMess + 1;
  993.     return noErr;
  994. }
  995.  
  996.  
  997.  
  998. /*----------------------------------------------------------------------------
  999.     GetGroupArrayArticleRanges 
  1000.     
  1001.     Query the NNTP server to get current article range info for designated 
  1002.     groups in a group list array.
  1003.  
  1004.     Entry:    groupArray = handle to group array.
  1005.             numGroups = number of groups in group array.
  1006.  
  1007.             Each group in the array to be updated is marked with status='x'.
  1008.  
  1009.     Exit:    function result = error code.
  1010.             Deleted groups are marked with status='d'.
  1011. ----------------------------------------------------------------------------*/
  1012.  
  1013. OSErr GetGroupArrayArticleRanges (TGroup **groupArray, short numGroups)
  1014. {
  1015.     NntpGroupInfoHandle info = nil;
  1016.     NntpErr nntpErr;
  1017.     NntpGroupInfoPtr q, qEnd;
  1018.     TGroup *p, *pEnd;
  1019.     long first, last;
  1020.     long numGroupsToUpdate = 0;
  1021.     CStr255 groupName;
  1022.     OSErr err = noErr;
  1023.     
  1024.     for (p = *groupArray, pEnd = p + numGroups; p < pEnd; p++)
  1025.         if (p->status == 'x') numGroupsToUpdate++;
  1026.         
  1027.     err = MyNewHandle(numGroupsToUpdate * sizeof(NntpGroupInfo), &info);
  1028.     if (err != noErr) goto exit;
  1029.     
  1030.     for (p = *groupArray, pEnd = p + numGroups, q = *info; p < pEnd; p++) {
  1031.         if (p->status == 'x') {
  1032.             q->offset = p->nameOffset;
  1033.             q++;
  1034.         }
  1035.     }
  1036.     
  1037.     nntpErr = NntpGetMultipleGroupInfo(gNewsStream, info, numGroupsToUpdate, gGroupNames);
  1038.     if (nntpErr != nntpNoErr) return ReportServerError(nntpErr);
  1039.     
  1040.     MyHLock(info);
  1041.     for (q = *info, qEnd = q + numGroupsToUpdate, p = *groupArray; q < qEnd; q++) {
  1042.         while (p->status != 'x') p++;
  1043.         if (q->ok) {
  1044.             first = q->first;
  1045.             last = q->last;
  1046.             strcpy(groupName, *gGroupNames + q->offset);
  1047.             if (first == 0 && last == 0) {
  1048.                 /* Special case empty group: set firstMess = lastMess + 1. */
  1049.                 p->firstMess = p->lastMess + 1;
  1050.                 err = AgeCache(groupName, 0x7fffffff);
  1051.                 if (err != noErr) goto exit;
  1052.             } else {
  1053.                 if (first <= 0) first = 1;
  1054.                 if (last < first) last = first - 1;
  1055.                 p->firstMess = first;
  1056.                 p->lastMess = last;
  1057.                 err = AgeCache(groupName, first);
  1058.                 if (err != noErr) goto exit;
  1059.             } 
  1060.             p->numUnread = p->lastMess - p->firstMess + 1;
  1061.         } else {
  1062.             p->status = 'd';
  1063.         }
  1064.         p++;
  1065.     }
  1066.     MyDisposeHandle(info);
  1067.     return noErr;
  1068.     
  1069. exit:
  1070.  
  1071.     MyDisposeHandle(info);
  1072.     return err;
  1073. }
  1074.  
  1075.  
  1076.  
  1077. /*----------------------------------------------------------------------------
  1078.     GetHeaders 
  1079.     
  1080.     Get header lines from the news server.
  1081.  
  1082.     Entry:    groupName = the group name.
  1083.             headerName = the header name.
  1084.             first = first article number.
  1085.             last = last article number.
  1086.             
  1087.     Exit:    function result = error code.
  1088.             *headers = handle to array of THeader records, or nil if
  1089.                 group does not exist.
  1090.             *strings = handle to header strings.
  1091.             *numHeaders = number of headers.
  1092. ----------------------------------------------------------------------------*/
  1093.  
  1094. OSErr GetHeaders (char *groupName, char *headerName, long first, long last, 
  1095.     THeader ***headers, Handle *strings, long *numHeaders)
  1096. {
  1097.     NntpErr nntpErr;
  1098.     NntpHeaderInfoHandle info;
  1099.     Handle str;
  1100.     long num;
  1101.     unsigned char *p, *pEnd, *q;
  1102.     
  1103.     nntpErr = NntpGetHeaders(gNewsStream, groupName, first, last, headerName,
  1104.         nil, nil, nil, &info, &str, &num);
  1105.     if (nntpErr != nntpNoErr) {
  1106.         if (nntpErr == nntpNoSuchGroupErr) {
  1107.             *headers = nil;
  1108.             return noErr;
  1109.         } else {
  1110.             return ReportServerError(nntpErr);
  1111.         }
  1112.     }
  1113.     
  1114.     /* Filter unprintable characters. */
  1115.     
  1116.     p = (unsigned char*)*str;
  1117.     pEnd = p + MyGetHandleSize(str);
  1118.     while (p < pEnd) {
  1119.         q = p;
  1120.         while (*p != 0) {
  1121.             if (*p >= ' ') {
  1122.                 *q++ = *p++;
  1123.             } else {
  1124.                 p++;
  1125.             }
  1126.         }
  1127.         *q = 0;
  1128.         p++;
  1129.     }
  1130.     
  1131.     MapLatin1ToMacHandle(str);
  1132.     
  1133.     *headers = (THeader**)info;
  1134.     *strings = str;
  1135.     *numHeaders = num;
  1136.     return noErr;
  1137. }
  1138.  
  1139.  
  1140.  
  1141. /*----------------------------------------------------------------------------
  1142.     SearchHeaders 
  1143.     
  1144.     Search header lines and returns matches. The search is for any 
  1145.     substring and is case-insensitive. The XPAT command is used if the
  1146.     server supports it and the "use XPAT" preference is turned on. 
  1147.     Otherwise, we do it the hard way.
  1148.  
  1149.     Entry:    groupName = the group name.
  1150.             headerName = the header name.
  1151.             first = first article number.
  1152.             last = last article number.
  1153.             pattern = search string. WARNING: This string is modified
  1154.                 by the function.
  1155.             
  1156.     Exit:    function result = error code.
  1157.             *headers = handle to array of THeader records, or nil if
  1158.                 group does not exist.
  1159.             *numHeaders = number of headers.
  1160. ----------------------------------------------------------------------------*/
  1161.  
  1162. static void BuildXPAT (char *pattern, char *regExp, short regExpLen)
  1163. {
  1164.     unsigned char *p, *q, *qEnd, *e;
  1165.     unsigned char equivs[256];
  1166.     short numEquiv;
  1167.     
  1168.     p = (unsigned char*)pattern;
  1169.     q = (unsigned char*)regExp;
  1170.     qEnd = q + regExpLen;
  1171.     
  1172.     if (q >= qEnd) goto exit;
  1173.     *q++ = '*';
  1174.     while (*p != 0) {
  1175.         GetEquivalentCharacters(*p, equivs, &numEquiv);
  1176.         if (numEquiv > 1) {
  1177.             if (q >= qEnd) goto exit;
  1178.             *q++ = '[';
  1179.         }
  1180.         e = equivs;
  1181.         while (*e != 0) {
  1182.             if (*e == '*' || *e == '?' || *e == '\\' || *e == '[' || *e == ']') {
  1183.                 if (q >= qEnd) goto exit;
  1184.                 *q++ = '\\';
  1185.             }
  1186.             if (q >= qEnd) goto exit;
  1187.             *q++ = *e++;
  1188.         }
  1189.         if (numEquiv > 1) {
  1190.             if (q >= qEnd) goto exit;
  1191.             *q++ = ']';
  1192.         }
  1193.         p++;
  1194.     }
  1195.     if (q >= qEnd) goto exit;
  1196.     *q++ = '*';
  1197.     if (q >= qEnd) goto exit;
  1198.     *q = 0;
  1199.     MapMacToLatin1Ptr(regExp, (char*)q - regExp);
  1200.     return;
  1201.     
  1202. exit:
  1203.  
  1204.     *(regExp + regExpLen - 1) = 0;
  1205.     return;
  1206. }
  1207.  
  1208. static Boolean MatchPattern (char *pattern, char *string)
  1209. {
  1210.     CStr255 str;
  1211.     
  1212.     MapLatin1ToMacStr(string, str);
  1213.     return MyIsASubstring(str, pattern);
  1214. }
  1215.  
  1216. OSErr SearchHeaders (char *groupName, char *headerName, long first, long last,
  1217.     char *pattern, THeader ***headers, short *numHeaders)
  1218. {
  1219.     NntpErr nntpErr;
  1220.     NntpHeaderInfoHandle info;
  1221.     Handle str;
  1222.     long num;
  1223.  
  1224.     nntpErr = NntpGetHeaders(gNewsStream, groupName, first, last, headerName,
  1225.         pattern, BuildXPAT, MatchPattern, &info, &str, &num);
  1226.     if (nntpErr != nntpNoErr) {
  1227.         if (nntpErr == nntpNoSuchGroupErr) {
  1228.             *headers = nil;
  1229.             return noErr;
  1230.         } else {
  1231.             return ReportServerError(nntpErr);
  1232.         }
  1233.     }
  1234.  
  1235.     MyDisposeHandle(str);
  1236.     *headers = (THeader**)info;
  1237.     *numHeaders = num;
  1238.     return noErr;
  1239. }
  1240.  
  1241.  
  1242.  
  1243. /*----------------------------------------------------------------------------
  1244.     DoGetServerInfo 
  1245.     
  1246.     Handle the "Get Server Info" command.
  1247.     
  1248.     Exit:    function result = error code.
  1249. ----------------------------------------------------------------------------*/
  1250.  
  1251. OSErr DoGetServerInfo (void)
  1252. {
  1253.     char str[1000];
  1254.     char *p, *pEnd, *q;
  1255.     Handle serverHelpResponse = nil;
  1256.     Handle serverInfoText = nil;
  1257.     Str255 dateStr, timeStr, versStr, title;
  1258.     CStr255 addrStr;
  1259.     unsigned long rawSecs;
  1260.     unsigned long ipAddr;
  1261.     CStr255 helloMsg;
  1262.     NntpErr nntpErr;
  1263.     CStr255 cmd, helpCmdResponse, fmt;
  1264.     long num;
  1265.     OSErr err = noErr;
  1266.     WindowPtr wind;
  1267.     
  1268.     MyICReadSharedPrefs(kICNNTPHost);
  1269.  
  1270.     GetPString(kStrServerInfoWindTitle, title);
  1271.     if (CheckTextWindowAlreadyOpen(title)) return noErr;
  1272.  
  1273.     HiliteMenu(0);
  1274.  
  1275.     GetDateTime(&rawSecs);
  1276.     IUDateString(rawSecs, abbrevDate, dateStr);
  1277.     IUTimeString(rawSecs, false, timeStr);
  1278.     err = GetVersionString(versStr);
  1279.     if (err != noErr) goto exit;
  1280.     NntpGetIPAddr(gNewsStream, &ipAddr);
  1281.     NntpGetHello(gNewsStream, helloMsg);
  1282.     sprintf(addrStr, "%ld.%ld.%ld.%ld", (ipAddr >> 24) & 0xff, (ipAddr >> 16) & 0xff, 
  1283.         (ipAddr >> 8) & 0xff, ipAddr & 0xff);
  1284.     nntpErr = NntpGetHelp(gNewsStream, &serverHelpResponse);
  1285.     if (nntpErr != nntpNoErr) return ReportServerError(nntpErr);
  1286.     NntpGetServerErrInfo(cmd, &num, helpCmdResponse);
  1287.     GetCString(kStrServerInfoFmt, fmt);
  1288.     p2cstr(dateStr);
  1289.     p2cstr(timeStr);
  1290.     p2cstr(versStr);
  1291.     p2cstr(gPrefs.newsServerName);
  1292.     sprintf(str, fmt, dateStr, timeStr, versStr, gPrefs.newsServerName, 
  1293.         addrStr, helloMsg, helpCmdResponse);
  1294.     c2pstr((char*)gPrefs.newsServerName);
  1295.  
  1296.     p = *serverHelpResponse + MyGetHandleSize(serverHelpResponse) - 1;
  1297.     while (p > *serverHelpResponse && *p == CR) p--;
  1298.     MySetHandleSize(serverHelpResponse, p - *serverHelpResponse + 1);
  1299.     
  1300.     for (p = *serverHelpResponse, pEnd = p + MyGetHandleSize(serverHelpResponse);
  1301.         p < pEnd;
  1302.         p++)
  1303.     {
  1304.         if (*p == '<') {
  1305.             q = p+1;
  1306.             while (q < pEnd && *q != '>' && *q != '@' && *q != CR) q++;
  1307.             if (q < pEnd && *q == '@') {
  1308.                 q++;
  1309.                 while (q < pEnd && *q != '>' && *q != CR) q++;
  1310.                 if (q < pEnd && *q == '>') {
  1311.                     *p = ' ';
  1312.                     *q = ' ';
  1313.                     break;
  1314.                 }
  1315.             }
  1316.         }
  1317.     }
  1318.         
  1319.     err = MyPtrToHand(str, &serverInfoText, strlen(str));
  1320.     if (err != noErr) goto exit;
  1321.     err = MyHandAndHand(serverHelpResponse, serverInfoText);
  1322.     if (err != noErr) goto exit;
  1323.     MyDisposeHandle(serverHelpResponse);
  1324.     serverHelpResponse = nil;
  1325.     MapLatin1ToMacHandle(serverInfoText);
  1326.     err = MakeNewTextWindow(title, 0, nil, serverInfoText, &wind);
  1327.     if (err != noErr) goto exit;
  1328.     MyDisposeHandle(serverInfoText);
  1329.     return noErr;
  1330.     
  1331. exit:
  1332.  
  1333.     MyDisposeHandle(serverHelpResponse);
  1334.     MyDisposeHandle(serverInfoText);
  1335.     return err;
  1336. }
  1337.  
  1338.  
  1339.  
  1340. /*----------------------------------------------------------------------------
  1341.     ResetNewsServerOptions 
  1342.     
  1343.     Reset the server options on the news server stream. This function must
  1344.     be called whenever the options change (e.g., when the user changes them
  1345.     in the prefs dialog).
  1346. ----------------------------------------------------------------------------*/
  1347.  
  1348. void ResetNewsServerOptions (void)
  1349. {
  1350.     NntpStreamOptions options;
  1351.  
  1352.     SetServerOptions(&options);
  1353.     if (gNewsStream != nil) NntpSetStreamOptions(gNewsStream, &options);
  1354. }
  1355.  
  1356.  
  1357.  
  1358. /*----------------------------------------------------------------------------
  1359.     Reauthenticate 
  1360.     
  1361.     Reauthenticate the user after a change in username or password.
  1362.     
  1363.     Exit:    function result = error code.
  1364. ----------------------------------------------------------------------------*/
  1365.  
  1366. OSErr ReAuthenticate (void)
  1367. {
  1368.     NntpStreamHandle oldStream;
  1369.     OSErr err = noErr;
  1370.  
  1371.     if (gNewsStream == nil) return noErr;
  1372.     if (!gPrefs.authAtStartup) {
  1373.         NntpAbort(gNewsStream);
  1374.     } else {
  1375.         oldStream = gNewsStream;
  1376.         gNewsStream = nil;
  1377.         err = StartNNTP();
  1378.         if (err == noErr) {
  1379.             NntpClose(oldStream);
  1380.         } else {
  1381.             gNewsStream = oldStream;
  1382.         }
  1383.     }
  1384.     return err;
  1385. }
  1386.